
import os
from whiff import gateway
from whiff import resolver
from whiff.middleware import displayTraceback
import root

def files(path):
    list = os.listdir(path)
    result = []
    for x in list:
        if not x.startswith('.') and not x.startswith('m2') and not x.endswith("~"):
            result.append(x)
    return result

def allfiles(*paths):
    D = {}
    for path in paths:
        if path is not None:
            if os.path.exists(path):
                fs = files(path)
                for fn in fs:
                    D[fn] = fn
    result = D.keys()
    result.sort()
    return result

class FileTestFinder:
    def __init__(self, rootDir):
        self.rootDir = rootDir
    def getFullPath(self, pathlist):
        filePath = pathlist[0]
        fullPath = os.path.join(self.rootDir, filePath)
        return fullPath
    def get(self, pathlist):
        fullPath = self.getFullPath(pathlist)
        if os.path.exists(fullPath):
            if os.path.isdir(fullPath):
                status = "directory"
            if os.path.isfile(fullPath):
                status = "file"
            return "other"
        else:
            status = "missing"
        return status
    def put(self, pathlist, value):
        raise gateway.AccessDenied, "put disallowed"

class FileContentFinder(FileTestFinder):
    def get(self, pathlist):
        fullPath = self.getFullPath(pathlist)
        return getContent(fullPath) #file(fullPath).read()

class DirectoryListingFinder:
    "directory listing for 2 way comparison between directories"
    def __init__(self, fromRoot, toRoot):
        self.fromRoot = fromRoot
        self.toRoot = toRoot
    def get(self, pathlist):
        filenameToStatus = {}
        path = pathlist[0]
        dir1 = os.path.join(self.fromRoot, path)
        dir2 = os.path.join(self.toRoot, path)
        list1 = list(files(dir1))
        list2 = list(files(dir2))
        list1.sort()
        list2.sort()
        for fn in list1:
            if not fn in list2:
                filenameToStatus[fn] = "deleted"
            else:
                path1 = os.path.join(dir1, fn)
                path2 = os.path.join(dir2, fn)
                if os.path.isdir(path1):
                    if os.path.isdir(path2):
                        if directoriesSame(path1, path2):
                            filenameToStatus[fn] = "sameDirectory"
                        else:
                            filenameToStatus[fn] = "changedDirectory"
                    else:
                        filenameToStatus[fn] = "directoryFile"
                elif os.path.isfile(path1):
                    if os.path.isfile(path2):
                        if filesSame(path1, path2):
                            filenameToStatus[fn] = "same"
                        else:
                            filenameToStatus[fn] = "changed"
                    elif os.path.isdir(path2):
                        filenameToStatus[fn] = "fileDirectory"
                    else:
                        filenameToStatus[fn] = "incomparable"
                else:
                    filenameToStatus[fn] = "unknown"
        for fn in list2:
            if not fn in list1:
                filenameToStatus[fn] = "new"
        items = filenameToStatus.items()
        items.sort()
        return items
    def put(self, pathlist, value):
        raise gateway.AccessDenied, "put disallowed"

class DirectoryMergeFinder:
    "directory listing for 3 way merge between directories"
    def __init__(self, oldRoot, localRoot, newRoot):
        self.oldRoot=oldRoot
        self.localRoot=localRoot
        self.newRoot=newRoot
    def get(self, pathList):
        path = pathList[0]
        if path.startswith("./"):
            path = path[2:]
        olddir = os.path.join(self.oldRoot, path)
        newdir = os.path.join(self.newRoot, path)
        localdir = os.path.join(self.localRoot, path)
        files = allfiles(olddir, newdir, localdir)
        allpaths = [ os.path.join(path, fn) for fn in files ]
        #result = [ merge3(f, olddir, localdir, newdir) for f in files ]
        result = [ merge3(p, self.oldRoot, self.localRoot, self.newRoot) for p in allpaths ]
        return result

def merge3(filename, oldRoot, localRoot, newRoot, doLinks=True):
    """
    Make a default 3 way merge decision by comparing old local and new file contents:
    NEW = keep new content
    LOCAL = keep local content
    MERGE = check manually
    """
    print "merge3", (filename, oldRoot, localRoot, newRoot)
    if filename.startswith("./"):
        filename = filename[2:]
    oldpath = os.path.join(oldRoot, filename)
    newpath = os.path.join(newRoot, filename)
    localpath = os.path.join(localRoot, filename)
    print "paths", (oldpath, newpath, localpath)
    (oldpath, oldcontent, oldisdir) = testContent(oldpath)
    (newpath, newcontent, newisdir) = testContent(newpath)
    (localpath, localcontent, localisdir) = testContent(localpath)
    if not localpath and not oldpath and not newpath:
        raise ValueError, "navigated to bad paths "+repr((filename, oldRoot, localRoot, newRoot))
    aredirs = False
    links = None
    if ((not localpath) or localisdir) and ((not newpath) or newisdir) and ((not oldpath) or oldisdir):
        aredirs = True
        # make decision for directories
        # if the decision ever changes default to "MERGE"
        decision = None
        files = allfiles(newpath, localpath, oldpath)
        print "checking", (newpath, localpath, oldpath)
        for fn in files:
            fp = os.path.join(filename, fn) #filename+"/"+fn
            (fp, d, op, cp, np, ad, lnk) = merge3(fp, oldRoot, localRoot, newRoot, doLinks=False)
            print "   got", (fn, d)
            if decision is None:
                decision = d
            elif decision!=d:
                decision = "MERGE"
            if decision=="MERGE":
                break # no need to look further
        if decision is None:
            decision = "NEW" # empty directory case
        print "for", (newpath, localpath, oldpath), decision
    elif (localpath and localisdir) or (newpath and newisdir) or (oldpath and oldisdir):
        raise ValueError, "don't know how to compare directory to file "+repr((oldpath, newpath, localpath))
    else:
        # default decision: keep NEW
        decision = "NEW"
        if localcontent!=oldcontent:
            if newcontent==oldcontent:
                # local modifications only: keep local
                decision = "LOCAL"
            elif newcontent!=localcontent:
                # all 3 differ: must check manually
                decision = "MERGE"
        else:
            print "DECISION=NEW BECAUSE OLD AND LOCAL CONTENT MATCH"
        # compute links
        if doLinks:
            links = (compareLink(filename, "old", oldpath, oldcontent, "local", localpath, localcontent)+
                     compareLink(filename, "old", oldpath, oldcontent, "new", newpath, newcontent)+
                     compareLink(filename, "local", localpath, localcontent, "new", newpath, newcontent))
    return (filename, decision, oldpath, localpath, newpath, aredirs, links)

def compareLink(filename, nameA, pathA, contentA, nameB, pathB, contentB):
    if contentA==contentB:
        return "%s and %s match; " % (nameA, nameB)
    url = "showDiff?filename=%s&nameA=%s&nameB=%s" % (filename, nameA, nameB)
    return '<a href="%s">[%s diff %s]</a>; ' % (url, nameA, nameB)

def testContent(path):
    print "getting content", repr(path)
    if os.path.isdir(path):
        return (path, None, True) # indicate directory
    text = getContent(path)
    if text is None:
        print "bad path", path
        return (None, None, None) # null out path name since file is absent
    return (path, text, False)

def filesSame(path1, path2):
    # brute force...
    text1 = getContent(path1) #file(path1).read()
    text2 = getContent(path2) #file(path2).read()
    if text1==text2:
        return True
    else:
        #pr "text1"
        #pr text1
        #pr "text2"
        #pr text2
        return False

def getContent(path):
    if not os.path.exists(path):
        return None
    return "".join(list(cleanLines(path)))

forbidden = ["$HeadURL", "$Id", "$URL", "@author", "@version"]

def cleanLines(path):
    f = file(path)
    firstline = f.readline()
    # skip all blank lines
    sf = firstline.strip()
    while not sf:
        firstline = f.readline()
        if not firstline:
            break
        sf = firstline.strip()
    # skip C style headers
    if firstline.startswith("/*"):
        firstline = f.readline()
        while firstline and firstline.find("*/")<0:
            firstline = f.readline()
    yield firstline
    line = f.readline()
    while line:
        if "$" in line:
            for nono in forbidden:
                if line.find(nono)>0:
                    trunc = line.find(nono)
                    line = line[:trunc]
            yield line
        else:
            yield line
        line = f.readline()
    
def directoriesSame(dir1, dir2):
    list1 = list(files(dir1))
    list2 = list(files(dir2))
    list1.sort()
    list2.sort()
    if list1!=list2:
        return False
    for fn in list1:
        path1 = os.path.join(dir1, fn)
        path2 = os.path.join(dir2, fn)
        if os.path.isdir(path1):
            if os.path.isdir(path2):
                # recursive compare
                if not directoriesSame(path1, path2):
                    return False
            else:
                return False
        elif os.path.isdir(path2):
            return False
        elif os.path.isfile(path1):
            if os.path.isfile(path2):
                if not filesSame(path1, path2):
                    return False
            else:
                return False
        elif os.path.isfile(path2):
            return False
        else:
            # ??? not files or dirs ... punt
            pass
    return True

def go(fromRoot, toRoot):
    import wsgiref.simple_server
    testapp = resolver.moduleRootApplication("/", root,
                                         exception_middleware=displayTraceback.__middleware__,
                                         on_not_found=None, # show traceback (could comment)
                                         )
    directoryfinder = DirectoryListingFinder(fromRoot, toRoot)
    testapp.registerResourceFinder(prefix="oldcontent", finder=FileContentFinder(fromRoot))
    testapp.registerResourceFinder(prefix="newcontent", finder=FileContentFinder(toRoot))
    testapp.registerResourceFinder(prefix="directory", finder=directoryfinder)
    print "start page at http://localhost:8888/showDirectory"
    print "serving wsgi at 8888"
    srv = wsgiref.simple_server.make_server('localhost', 8888, testapp)
    srv.serve_forever()

if __name__=="__main__":
    from sys import argv
    go(argv[1], argv[2])
